﻿using BMC.ARSystem;
//------------------------------------------------------------------
// System Name:    NRemedy
// Component:      NRemedy
// Create by:      Parker Zhou (parkerz@wicresoft.com)
// Create Date:    2012-04-10
//------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace NRemedy
{

    /// <summary>
    /// ModelBinder is responsed bind the raw fieldvalueList into T
    /// and unbind the T down to fieldvaluelist
    /// </summary>
    /// <typeparam name="T">Model Type</typeparam>
    public class ModelBinder<T> : IModelBinder<T>
    {

        public FieldValueList UnBindForUpdate(T Model)
        {
            return UnBind(Model, new PropertyFilterDelegate(PropertyFilterForChanged));
        }

        public FieldValueList UnBind(T Model)
        {
            return UnBind(Model, null);
        }

        public FieldValueList UnBind(T Model, PropertyFilterDelegate filter)
        {
            if (Model == null)
                throw new ArgumentNullException("Model");
            FieldValueList list = new FieldValueList();

            ARFieldAttribute arField = null;
            PropertyInfo property = null;

            foreach (PropertyInfoAndARAttribute propertyAndAR in BinderHelper.GetPropertyInfos(typeof(T), false))
            {

                arField = propertyAndAR.ARAttribute;
                property = propertyAndAR.Property;

                //if filter supplied,check if being filter
                if (filter != null && filter(Model as ARBaseForm, property) == false)
                    continue;
                //db id
                uint id = arField.DatabaseID;
                if (list.Contains(id)) throw new DuplicateIDException(id);
                //type
                int typeId = (int)arField.DataType;
                object value = property.GetValue(Model, null);
                list.Add(id, NETConvertToAR(value, arField.DataType), typeId);
            }
            return list;
        }

        public T Bind(FieldValueList valueList)
        {
            if (valueList == null) throw new ArgumentNullException("valueList");

            T model = Activator.CreateInstance<T>();

            List<PropertyInfoAndARAttribute> properties = BinderHelper.GetPropertyInfos(typeof(T), true);

            PropertyInfoAndARAttribute propertyAndAR = null;
            ARFieldAttribute arField = null;
            PropertyInfo property = null;

            foreach (uint item in valueList.Keys)
            {
                propertyAndAR = properties.FirstOrDefault(o => o.ARAttribute.DatabaseID == item);
                if (propertyAndAR == null)
                    throw new InvalidCastException(string.Format("valueList cannot cast to model : {0}", typeof(T).FullName),
                        new Exception(string.Format("DatabaseID : {0} in model : {1} cannot be found in valueList",
                            propertyAndAR.ARAttribute.DatabaseID, typeof(T).FullName)));
                arField = propertyAndAR.ARAttribute;
                property = propertyAndAR.Property;
                object value = valueList[arField.DatabaseID];
                int arType = valueList.GetValueType(arField.DatabaseID);
                property.SetValue(model, ARConvertToNET(property.PropertyType, arField.DataType, value), null);
            }

            return model;
        }


        public List<T> BindList(ArrayList list)
        {
            if (list == null) throw new ArgumentNullException("valueList");


            List<T> listM = new List<T>();
            T model = default(T);
            FieldValueList fieldValue;

            foreach (var item1 in list)
            {

                model = Activator.CreateInstance<T>();

                List<PropertyInfoAndARAttribute> properties = BinderHelper.GetPropertyInfos(typeof(T), true);

                PropertyInfoAndARAttribute propertyAndAR = null;
                ARFieldAttribute arField = null;
                PropertyInfo property = null;
                arField = propertyAndAR.ARAttribute;
                property = propertyAndAR.Property;

                fieldValue=  (FieldValueList)item1;

                foreach (uint item in fieldValue.Keys)
                {
                    propertyAndAR = properties.FirstOrDefault(o => o.ARAttribute.DatabaseID == item);
                    if (propertyAndAR == null)
                        throw new InvalidCastException(string.Format("valueList cannot cast to model : {0}", typeof(T).FullName),
                            new Exception(string.Format("DatabaseID : {0} in model : {1} cannot be found in valueList",
                                propertyAndAR.ARAttribute.DatabaseID, typeof(T).FullName)));

                    property.SetValue(model, ARConvertToNET(property.PropertyType, arField.DataType, fieldValue[arField.DatabaseID]), null);
                    listM.Add(model);
                }

            }
            return listM;
        }


        private string ConvertString(string str)
        {
            if (str == null) return "";
            return Global.CharacterEncoding.GetString(System.Text.Encoding.UTF8.GetBytes(str));
        }

        /// <summary>
        /// convert .net value to ar value
        /// </summary>
        /// <param name="propValue">.net value</param>
        /// <param name="type">ar type</param>
        /// <returns>ar value</returns>
        public object NETConvertToAR(object propValue, ARType type)
        {
            if (type == ARType.None)
                throw new ArgumentOutOfRangeException("type");
            if (propValue == null)
                return null;

            Type valueType = propValue.GetType();
            if ((!valueType.IsEnum && valueType.FullName != ARTypeMap.ARTypeToNETType(type).FullName)
                //for enum 
                || (valueType.IsEnum && type != ARType.SelectionField))
                throw new InvalidCastException(string.Format("propValue : {0} cannot cast to the ARType : {1}", propValue, type));
            switch (type)
            {
                case ARType.IntegerField:
                    return (Int32)propValue;
                case ARType.RealField:
                    return (Double)propValue;
                case ARType.CharacterField:
                    return ConvertString((String)propValue);
                case ARType.DiaryField:
                    return (String)propValue;
                case ARType.SelectionField:
                    return (Int32)propValue;    //enum cast to int32
                case ARType.DateTimeField:
                    return ((DateTime)propValue).ToUniversalTime();
                case ARType.DecimalField:
                    return (Decimal)propValue;
                case ARType.AttachmentField:
                    return (Attachment)propValue;
                case ARType.CurrencyField:
                    return (CurrencyValue)propValue;
                case ARType.DateOnlyField:
                    return ((DateValue)propValue).JulianDate;
                case ARType.TimeOnlyField:
                    return ((TimeOfDayValue)propValue).TotalSeconds;
                default:
                    throw new ArgumentOutOfRangeException("type");
            }
        }

        /// <summary>
        /// convert ar value to .net value
        /// </summary>
        /// <param name="NETType"></param>
        /// <param name="ARType"></param>
        /// <param name="ARValue"></param>
        /// <returns>.net value</returns>
        public object ARConvertToNET(Type NETType, ARType ARType, object ARValue)
        {
            if (NETType == null)
                throw new ArgumentNullException("NETType");
            if (ARValue == null || ARValue is System.DBNull)
            {
                if (NETType.IsValueType) return default(ValueType);
                return null;
            }
            if (NETType.IsGenericType && NETType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                NETType = NETType.GetGenericArguments()[0];
            }
            if ((!NETType.IsEnum && ARTypeMap.NETTypeToARType(ARType).FullName != NETType.FullName)
                    || (NETType.IsEnum && ARType != ARType.SelectionField))
                throw new InvalidCastException(string.Format("ARValue : {0} with type of {1} cannot cast to the NETType : {2}", ARValue, ARType, NETType));
            switch (ARType)
            {
                case ARType.IntegerField:
                    return (Int32)ARValue;
                case ARType.RealField:
                    return (Double)ARValue;
                case ARType.CharacterField:
                    return (String)ARValue;
                case ARType.DiaryField:
                    return (DiaryList)ARValue;
                case ARType.SelectionField:
                    return Enum.ToObject(NETType, ARValue);    //int32 cast to enum
                case ARType.DateTimeField:
                    return ((DateTime)ARValue).ToLocalTime();
                case ARType.DecimalField:
                    return (Decimal)ARValue;
                case ARType.AttachmentField:
                    return (Attachment)ARValue;
                case ARType.CurrencyField:
                    return (CurrencyValue)ARValue;
                case ARType.DateOnlyField:
                    return (DateValue)ARValue;
                case ARType.TimeOnlyField:
                    return (TimeOfDayValue)ARValue;
                default:
                    throw new ArgumentOutOfRangeException("type");
            }

        }

        /// <returns>true : pass; false : block</returns>
        private bool PropertyFilterForChanged(ARBaseForm model, PropertyInfo pi)
        {
            if (model == null)
                throw new ArgumentNullException("model", "model is probably not inherit from ARBaseForm");
            if (pi == null)
                throw new ArgumentNullException("pi");
            if (model.ChangedProperties.ContainsKey(pi.Name))
                return model.ChangedProperties[pi.Name];
            else
                return false;
        }


        public ARFieldAttribute GetARAttributeField(PropertyInfo pi, ModelBinderAccessLevel accessLevel)
        {
            throw new NotImplementedException();
        }
    }
}
